#include <iostream>
#include "herm/base/socket/socket_base.h"
#include "herm/base/socket/stream.h"
#include "herm/base/utils/misc.h"
#include "tcp_session.h"
#include "accept_handler.h"
#include "tc_peer.h"

TCPeer::TCPeer() : m_peerParam(false, false)
{
	m_tmpBuf = nullptr;
	m_tcpSessionObjMgr = nullptr;
	m_nbcTCPSessionObjMgr = nullptr;
}

TCPeer::~TCPeer()
{
	HERM_DELETE(m_tcpSessionObjMgr);
	HERM_DELETE(m_nbcTCPSessionObjMgr);
	HERM_DELETE_ARRAY(m_tmpBuf);
}

void TCPeer::Reset(herm::Multiplexor* multiplexor, herm::IListener* listener,
	const herm::TCPeerParam& peerParam)
{
	m_localAddr.Init();

	m_listner = listener;
	m_peerParam = peerParam;
	delete m_tmpBuf;
	m_tmpBuf = new char[m_peerParam.recvTCPBufSize];

	delete m_tcpSessionObjMgr;

	m_tcpSessionObjMgr = new TCPSessionObjMgr(peerParam.sessionCount);
	for (int i = 0; i < peerParam.sessionCount; i++)
	{
		TCPSession* session = m_tcpSessionObjMgr->GetObj(i);
		session->Init(multiplexor, this, i, &m_peerParam, m_tmpBuf); 
	}

	delete m_nbcTCPSessionObjMgr;
	m_nbcTCPSessionObjMgr = new NBCTCPSessionObjMgr(peerParam.nbConnSessionCount);
	for (int i = 0; i < peerParam.nbConnSessionCount; i++)
	{
		NonblockConnectTCPSession* session = m_nbcTCPSessionObjMgr->GetObj(i);
		session->Init(multiplexor, this, i, &m_peerParam, m_tmpBuf); 
	}
}

herm::ISession* TCPeer::CreateSession(const std::string& servName, 
	const herm::Address& remoteAddr, bool needReconnect, bool isBlock, 
	herm::INBCNotifier* notifier)
{
	if (isBlock)
	{
		return CreateSession(servName, remoteAddr, needReconnect);
	}
	else
		return CreateNBConnSession(remoteAddr, notifier);
}

static void DoConnect(bool isReconnect, const std::string& servName, TCPSession* tcSession,
	herm::Address& localAddr, const herm::Address& remoteAddr, bool needReconnect)
{
	try
	{
		herm::Connector connector;
		herm::Stream stream;
		herm::Address nullAddr;
	//	if (localAddr == nullAddr)
	//	{
			connector.Connect(remoteAddr, stream,  true); 
			stream.GetLocalAddress(localAddr);
	//	}
	//	else
	//		connector.Connect(localAddr, remoteAddr, stream, true);

		if (!isReconnect)
			tcSession->Reset(stream.Detach(), needReconnect);
		else
			tcSession->ResetByReconnect(stream.Detach(), needReconnect);

		if (servName != "")
		{
			std::cout << "Connect " << servName << "[" << remoteAddr.IP() 
				<< " : " << remoteAddr.Port() << "] successfully.\n";
		}
	}
	catch (...)
	{
		std::cout << "Connect " << servName << "[" << remoteAddr.IP() 
			<< " : " << remoteAddr.Port() << "] failed.\n";
		herm::Sleep(1000000); 
		throw;
	}
}

void TCPeer::Reconnect(TCPSession* tcSession)
{
	if (!tcSession)
		return;

//	tcSession->PrepareReconnect();

	herm::ISession* tcSessionBase = tcSession;
	const herm::Address& remoteAddr = tcSessionBase->GetRemoteAddr();
	DoConnect(true, "TCPServer", tcSession, m_localAddr, remoteAddr, true);
}

herm::ISession* TCPeer::CreateSession(const std::string& servName, 
	const herm::Address& remoteAddr, bool needReconnect)
{
	TCPSession* session = AcquireSessionObj<TCPSession>(m_tcpSessionObjMgr);
	if (!session)
		throw HERM_SOCKET_EXCEPTION(herm::ST_OTHER);

	try
	{
		DoConnect(false, servName, session, m_localAddr, remoteAddr, needReconnect);
	}
	catch (const herm::SocketException& e) // catch exception for deleting sh
	{
		herm::ISession* tmpSession = reinterpret_cast<herm::ISession*>(session); 
		m_tcpSessionObjMgr->Release(tmpSession->GetIndex());
		std::string err = e.Desc();
		throw;
	}

	return session;
}

herm::ISession* TCPeer::CreateNBConnSession(const herm::Address& remoteAddr, herm::INBCNotifier* notifier)
{
    NonblockConnectTCPSession* session = AcquireSessionObj<NonblockConnectTCPSession>(m_nbcTCPSessionObjMgr);
	if (!session)
		throw HERM_SOCKET_EXCEPTION(herm::ST_OTHER);

	try
	{
		herm::Connector connector;
		herm::Stream stream;
		herm::Address nullAddr;
		if (m_localAddr == nullAddr)
		{
			connector.ConnectNB(remoteAddr, stream);
			stream.GetLocalAddress(m_localAddr);
		}
		else
			connector.ConnectNB(m_localAddr, remoteAddr, stream, true);

		session->Reset(stream.Detach(), notifier);
	}
	catch (const herm::SocketException& e) // catch exception for deleting sh
	{
        m_nbcTCPSessionObjMgr->Release(reinterpret_cast<herm::ISession*>(session)->GetIndex());
		std::string err = e.Desc();
		throw;
	}

	return session;
}

void TCPeer::DestroySession(int sessinIdx)
{
	TCPSession* tcpSession = m_tcpSessionObjMgr->GetObj(sessinIdx);
	if (tcpSession)
	{
		tcpSession->Finish();
		m_tcpSessionObjMgr->Release(sessinIdx);
	}
}

herm::IListener* TCPeer::GetListener() const
{
	return m_listner;
}
